﻿
namespace Aeon.Emulator.Video
{
    /// <summary>
    /// Emulates the VGA DAC which provides access to the palette.
    /// </summary>
    internal sealed class Dac
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Dac class.
        /// </summary>
        public Dac()
        {
            Reset();
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the full 256-color palette.
        /// </summary>
        public uint[] Palette
        {
            get { return palette; }
        }
        /// <summary>
        /// Gets or sets the current palette read index.
        /// </summary>
        public byte ReadIndex
        {
            get
            {
                return readIndex;
            }
            set
            {
                readIndex = value;
                readChannel = 0;
            }
        }
        /// <summary>
        /// Gets or sets the current palette write index.
        /// </summary>
        public byte WriteIndex
        {
            get
            {
                return writeIndex;
            }
            set
            {
                writeIndex = value;
                writeChannel = 0;
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Reads the next channel in the current color.
        /// </summary>
        /// <returns>Red, green, or blue channel value.</returns>
        public byte Read()
        {
            uint color = palette[readIndex];
            readChannel++;
            if(readChannel == 1)
                return (byte)((color >> 18) & 0x3F);
            else if(readChannel == 2)
                return (byte)((color >> 10) & 0x3F);
            else
            {
                readChannel = 0;
                readIndex++;
                return (byte)((color >> 2) & 0x3F);
            }
        }
        /// <summary>
        /// Writes the next channel in the current color.
        /// </summary>
        /// <param name="value">Red, green, or blue channel value.</param>
        public void Write(byte value)
        {
            writeChannel++;
            if(writeChannel == 1)
            {
                palette[writeIndex] &= 0xFF00FFFF;
                palette[writeIndex] |= (uint)((value & 0x3F) << 18);
            }
            else if(writeChannel == 2)
            {
                palette[writeIndex] &= 0xFFFF00FF;
                palette[writeIndex] |= (uint)((value & 0x3F) << 10);
            }
            else
            {
                palette[writeIndex] &= 0xFFFFFF00;
                palette[writeIndex] |= (uint)((value & 0x3F) << 2);
                writeChannel = 0;
                writeIndex++;
            }
        }
        /// <summary>
        /// Resets the colors to the default 256-color VGA palette.
        /// </summary>
        public void Reset()
        {
            byte[] source = Properties.Resources.VgaPalette;
            for(int i = 0; i < 256; i++)
            {
                uint r = source[i * 3];
                uint g = source[i * 3 + 1];
                uint b = source[i * 3 + 2];
                palette[i] = b | (g << 8) | (r << 16);
            }
        }
        /// <summary>
        /// Sets a color to the specified RGB values.
        /// </summary>
        /// <param name="index">Index of color to set.</param>
        /// <param name="r">Red component.</param>
        /// <param name="g">Green component.</param>
        /// <param name="b">Blue component.</param>
        public void SetColor(byte index, byte r, byte g, byte b)
        {
            uint red = (r & 0x3Fu) << 18;
            uint green = (g & 0x3Fu) << 10;
            uint blue = (b & 0x3Fu) << 2;

            palette[index] = red | green | blue;
        }
        #endregion

        #region Private Fields
        private readonly uint[] palette = new uint[256];
        private int readChannel;
        private int writeChannel;
        private byte readIndex;
        private byte writeIndex;
        #endregion
    }
}
